Python 模拟执行 JavaScript 教程

目录

  1. 为什么需要这技能?
  2. 环境快速搭建
  3. 实战:破解某球星站加密Token
  4. 避坑指南+性能小技巧
  5. 总结

为什么需要这技能?

现代Web开发的核心逻辑已经不是全写在后端返回HTML了——前端渲染、接口参数加密、动态cookie生成全是JS的活儿。

举几个爬虫工程师或全栈摸鱼时常用的场景:

  • 爬B站番剧/评论:需要先解析bili_jct_signature这类加密参数
  • 爬电商平台商品库存:需要获取动态生成的anti_scrape_token
  • 补全前后端分离项目的自动化测试:Python后端+前端加密需要模拟验证

今天我们就用PyExecJS(最通用的轻量级库)来解决这类问题,全程避开数学公式,重点讲「怎么把前端JS代码无缝搬到Python里跑」。


环境快速搭建

核心依赖

我们需要:

  1. PyExecJS库:Python和JS运行环境的中间桥梁
  2. JS运行环境:推荐Node.js(V8引擎,支持ES6+和第三方加密库,比Windows自带的JScript好用100倍)

一步步安装

1. 安装PyExecJS

pip install PyExecJS

2. 安装Node.js

直接去官网下载LTS版本(稳定不折腾):

https://nodejs.org/

安装完成后打开终端验证(Windows用PowerShell/CMD,Mac/Linux用Terminal):

node -v  # 输出版本号,比如v20.x.x
npm -v   # 可选,但如果要后续装第三方库会用到

3. 强制验证JS环境(关键!)

很多新手装完PyExecJS后,默认会用Windows自带的JScript(旧版不支持ES6+),这里要写个简单的代码强制检查并绑定Node.js

import execjs

# 打印当前可用的所有JS运行环境
print("可用的JS环境:", execjs.runtimes().keys())

# 强制指定用Node.js
ctx = execjs.get(execjs.runtime_names.Node)
print("当前绑定的JS环境:", ctx.name)  # 必须输出类似 "Node.js (V8)"

实战:破解某球星站加密Token

我们用官方提供的SPA7爬虫练习站https://spa7.scrape.center/)来练手——这个网站的每个球星卡片都有一个加密的`token`,只有拿到它才能请求完整的球星数据。

第一步:前端加密逻辑逆向

先别急着写Python!打开浏览器的开发者工具(F12),跟着我一步步找加密代码:

  1. 点击「Network」标签
  2. 刷新页面,找返回完整数据的请求(一般是带apidetail后缀的)
  3. 看请求参数,里面有个明显的加密字段token
  4. 点击「Initiator」标签,找到生成这个请求的JS文件位置
  5. 点进去后用Ctrl+F搜索getToken(加密函数的常见命名)

第二步:提取加密代码

找到加密函数后,我们发现它依赖了crypto-js这个第三方库来做DES加密,那我们需要做两个准备:

  1. 下载完整的crypto-js.min.js(压缩版,体积小)
  2. 把加密函数和crypto-js.min.js合并成一个JS文件,或者手动处理全局变量挂载

第三步:Python无缝调用

先整理加密文件(crypto_utils.js)

注意:Node.js默认不会把第三方库的内容挂载到全局对象,所以这里我们手动把压缩后的crypto-js包在一个自执行函数里,并返回CryptoJS

// 1. 手动挂载全局CryptoJS(解决Node.js环境下的变量问题)
var CryptoJS = (function() {
    // 这里粘贴完整的 crypto-js.min.js 内容
    // 为了演示省略,建议去 npm 官网下载压缩版
    return e();
})();

// 2. 从练习站提取的加密函数
function getToken(player) {
    // 固定密钥(练习站逆向出来的,真实项目要自己找)
    const key = "XwKsGlMcdPMEhR1B";
    // 只提取加密需要的字段
    const plainObj = {
        name: player.name,
        birthday: player.birthday,
        height: player.height,
        weight: player.weight
    };
    // 转成JSON字符串
    const plainText = JSON.stringify(plainObj);
    // 执行DES-ECB-Pkcs7加密
    const encrypted = CryptoJS.DES.encrypt(
        CryptoJS.enc.Utf8.parse(plainText),
        CryptoJS.enc.Utf8.parse(key),
        {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7
        }
    );
    // 返回Base64编码后的加密结果
    return encrypted.toString();
}

再写Python调用代码

import execjs

def generate_spa7_token(player_info):
    # 1. 预编译JS文件(只编译一次,后续复用性能更高)
    with open('crypto_utils.js', 'r', encoding='utf-8') as f:
        js_ctx = execjs.compile(f.read())
    
    # 2. 直接调用JS函数
    token = js_ctx.call('getToken', player_info)
    return token

if __name__ == "__main__":
    # 练习站的球星数据
    lebron = {
        "name": "LeBron James",
        "birthday": "1984-12-30",
        "height": "2.06m",
        "weight": "113.4kg"
    }
    
    # 生成Token
    spa7_token = generate_spa7_token(lebron)
    print(f"加密成功!Token:\n{spa7_token}")

避坑指南+性能小技巧

1. 避坑:CryptoJS未定义

问题原因:Node.js环境下,直接require('crypto-js')不会挂载到全局,或者我们压缩后的代码没有正确返回。 解决方案

  • 用上面的「自执行函数+全局挂载」方法
  • 或者在Python里通过ctx.eval("const CryptoJS = require('crypto-js')")(前提是先在当前目录下npm install crypto-js

2. 避坑:中文字符乱码

问题表现:如果加密对象里有中文,生成的Token在浏览器里验证失败。 解决方案

  • 确保JS文件是UTF-8 without BOM编码(不要用Windows记事本的默认编码)
  • Python读取JS文件时必须指定encoding='utf-8'

3. 性能优化:别每次都编译JS!

PyExecJS的编译过程是最耗时的(每次都会启动一个Node.js子进程),所以对于频繁调用的场景(比如爬取1000个球星的数据),必须:

  • execjs.compile()放在循环外,只执行一次
  • 复用同一个ctx对象

优化后的代码片段:

import execjs
import time

# 循环外预编译
with open('crypto_utils.js', 'r', encoding='utf-8') as f:
    js_ctx = execjs.compile(f.read())

# 模拟爬取10个球星
players = [
    {"name": "LeBron James", "birthday": "1984-12-30", "height": "2.06m", "weight": "113.4kg"},
    # ...省略另外9个球星的数据...
]

start_time = time.time()
for p in players:
    token = js_ctx.call('getToken', p)
end_time = time.time()

print(f"优化后爬取10个球星耗时:{end_time - start_time:.2f}s")
# 对比:每次都编译的话,耗时会变成10倍以上

总结

通过今天的教程,我们掌握了:

  1. PyExecJS的环境搭建(强制绑定Node.js是关键)
  2. 前端加密逻辑的简单逆向(练习站)
  3. 加密代码的无缝迁移(解决全局变量、编码问题)
  4. 基础的性能优化(复用预编译的JS上下文)

如果遇到更复杂的加密(比如混淆后的JS、WebAssembly),可以:

  • 先学习JS混淆的逆向工程(比如AST还原)
  • 或者用Selenium/Playwright这种浏览器自动化工具(虽然性能慢,但不用逆向加密逻辑)

完整示例代码:GitHub仓库